///|
fn is_optional(x : AttributeMeta) -> Bool {
  match (x.parameter.kind, x.attr_kind) {
    (Optional(_), _) | (_, BooleanAttribute) => true
    _ => false
  }
}

///|
fn generate_type(ty : MbtType) -> String {
  match ty {
    String => "String"
    Integer => "Int"
    Boolean => "Bool"
    InputType => "InputType"
    GenericMsg => "M"
    Function(params, ret) =>
      "(" + params.map(generate_type).join(", ") + ") -> " + generate_type(ret)
    Mouse => "Mouse"
    Scroll => "Scroll"
  }
}

///|
fn generate_param(param : MbtParameter) -> String {
  let name = param.argument
  let anno = match param.kind {
    Positional(ty) => " : " + generate_type(ty)
    Optional(ty) => "? : " + generate_type(ty)
    Labeled(ty, default=None) => "~ : " + generate_type(ty)
    Labeled(ty, default=Some(value)) =>
      "~ : " + generate_type(ty) + " = " + value
  }
  name + anno
}

///|
fn Attribute::get_handler_wrapper(x : Self) -> String? {
  match x {
    Onclick => Some("click_event")
    Oninput => Some("input_event")
    Onchange => Some("change_event")
    Ondblclick => Some("dblclick_event")
    Onmousedown => Some("mouse_down_event")
    Onmouseup => Some("mouse_up_event")
    Onmousemove => Some("mouse_move_event")
    Onmouseenter => Some("mouse_enter_event")
    Onmouseover => Some("mouse_over_event")
    Onmouseleave => Some("mouse_leave_event")
    Onmouseout => Some("mouse_out_event")
    Onscroll => Some("scroll_event")
    _ => None
  }
}

///|
fn generate_wrapper(html : HtmlMeta) -> String {
  let attrs = attribute_meta_list.filter(x => match x.target {
    Global =>
      match x.attr_kind {
        EventHandlerAttribute => x.attribute.get_handler_wrapper() is Some(_)
        _ => true
      }
    Element(elems) if elems.contains(html.html) =>
      match x.attr_kind {
        EventHandlerAttribute => x.attribute.get_handler_wrapper() is Some(_)
        _ => true
      }
    _ => false
  })
  let params = attrs.map(x => generate_param(x.parameter)).join(", ")
  let optional_args = attrs
    .filter(is_optional)
    .map(x => match x.attr_kind {
      StringAttribute =>
        "\{x.parameter.argument}.map(attribute(\"\{x.name}\", _))"
      BooleanAttribute =>
        "if \{x.parameter.argument} { Some(attribute(\"\{x.name}\", \"\")) } else { None }"
      EventHandlerAttribute =>
        "\{x.parameter.argument}.map(\{x.attribute.get_handler_wrapper().unwrap()}(_))"
      StringProperty =>
        "\{x.parameter.argument}.map(x => property(\"\{x.name}\", String(x)))"
      // DoubleProperty => 
      //   "\{x.parameter.argument}.map(x => property(\"\{x.name}\", Floating(x)))"
    })
    .join(", ")
  let positional_args = attrs
    .filter(x => not(is_optional(x)))
    .map(x => {
      let value_expr = match x.attr_kind {
        StringAttribute => "attribute(\"\{x.name}\", \{x.parameter.argument})"
        BooleanAttribute => panic()
        EventHandlerAttribute =>
          "\{x.attribute.get_handler_wrapper().unwrap()}(_)"
        StringProperty =>
          "property(\"\{x.name}\", String(\{x.parameter.argument}))"
      }
      "attribute(\"\{x.name}\", \{value_expr})"
    })
    .join(", ")
  let body = "make_node(\"\{html.name}\", positional=[\{positional_args}], optional=[\{optional_args}], children)"
  "///|\npub fn[M] \{html.wrapper_name}(\{params}, children : Array[Html[M]]) -> Html[M] { \{body} }"
}

///|
fn main {
  println(
    "// This file is auto-generated by src/experimental/html/internal/codegen/generator.mbt, do not edit it directly.",
  )
  html_meta_list.iter().map(generate_wrapper).join("\n") |> println
}
